#pragma once

#include "Log.h"
#include "Error.h"
#include <string>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

class ListenSocket {
public:
    constexpr static int fullConnectedQueueLength = 32; //* 该值为listen的第二个参数，服务器能够同时维持的全连接数量为该值 + 1
    constexpr static int DefaultListenSockfd = -1; // _listen_sockfd的默认值;

private:
    int _listen_sockfd;

public:
    ListenSocket()
        : _listen_sockfd(DefaultListenSockfd)
    {}

    ~ListenSocket() {
        if (_listen_sockfd != DefaultListenSockfd) ::close(_listen_sockfd);
    }

public:
    // 创建listen套接字, 是一种懒加载的思想
    void create() {
        _listen_sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP套接字
        if (_listen_sockfd < 0) { // 创建套接字失败
            logMessage(FATAL, "create _listen_sockfd err!");
            exit(SOCKET_ERR);
        }
        logMessage(NORMAL, "create _listen_sockfd success: %d", _listen_sockfd);

        int optval = 1; //* 可以为任意一个非零值，本质上是用int类型来代替bool类型。非零为真，零为假。非零代表允许(开启)bool型选项，零表示不允许(关闭)bool型选项

        //task 设置sockfd的地址和端口复用。
        //* 避免服务器因故障终止时(服务器主动退出)，会因为原本服务器的连接处于time_wait状态(主动退出的一方才会处于该状态)，而不能绑定原来的端口而及时重启(*服务端的端口号应是固定的)。
        setsockopt(_listen_sockfd, SOL_SOCKET /* 选项设置的层级，想要在套接字层面上设置选项就要用该宏 */, 
        SO_REUSEADDR | SO_REUSEPORT /* 允许sockfd在time_wait状态时，其他sockfd能够复用它绑定的ip地址和端口，SO_REUSEPORT不设置也可以达到目标效果 */, 
        &optval /* 非零值表示开启上述选项 */, sizeof(optval));

    }


    // 给listen套接字绑定端口号
    void bind(const uint16_t& port) {
        struct sockaddr_in host;
        bzero(&host, sizeof(host));
        host.sin_family = AF_INET;
        host.sin_port = htons(port);
        host.sin_addr.s_addr = htonl(INADDR_ANY); // 不绑定特定ip

        if (::bind(_listen_sockfd, (struct sockaddr*)&host, sizeof(host)) < 0) {
            logMessage(FATAL, "bind _listen_sockfd %d err!", _listen_sockfd);
            exit(BIND_ERR);
        }
        logMessage(NORMAL, "bind _listen_sockfd %d success!", _listen_sockfd);
    }

    // 将listen套接字设定为监听状态
    void listen() {
        if (::listen(_listen_sockfd, fullConnectedQueueLength) < 0) {
            logMessage(FATAL, "sockfd %d listen state set fail!", _listen_sockfd);
            exit(LISTEN_ERR);
        }
        logMessage(NORMAL, "sockfd %d listen state set success!", _listen_sockfd);
    }

    /**
     * 从监听套接字中获取新连接
     * 
     * @param client_ip 输出型参数，返回接收到的新连接的ip地址
     * @param client_port 输出型参数，返回接收到的新连接的端口号
     * 
     * @return 获得的新连接的sockfd(>= 0)，获取失败会返回-1，errno被设置
    */
    int accept(std::string* client_ip, uint16_t* client_port) {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        int sockfd = ::accept(_listen_sockfd, (struct sockaddr*)&client, &len);
        if (sockfd < 0) {
            // 如果与客户端建立连接失败了，不需要退出
            // 连接建立失败是常事，如果但凡有一个连接失败服务端就退出的话
            // 那么其他客户也都别用了
            // 继续接收下一个连接请求就行
            logMessage(ERROR, "accept link request err!");
        }
        else {
            logMessage(NORMAL, "accept a new link request success, the new sockfd is: %d", sockfd);
            *client_ip = std::string(inet_ntoa(client.sin_addr)); // 将网络字节序的ip地址(便于传输和给电脑看)转化为点分十进制ip(方便程序员看)
            *client_port = ntohs(client.sin_port);
        }

        return sockfd;
    }

    // 关闭listen套接字
    void close() {
        if (_listen_sockfd != DefaultListenSockfd) ::close(_listen_sockfd);
    }

    // 得到被封装的_listen_sockfd
    const int& getFD() const {
        return _listen_sockfd;
    }

};//@ end class ListenSocket